﻿<Assembly: System.Windows.Markup.XmlnsDefinition("http://wpfglue.wordpress.com/framework", "WPFGlue.Framework")> 
<Assembly: System.Windows.Markup.XmlnsPrefix("http://wpfglue.wordpress.com/framework", "f")> 
Namespace Framework
    Public Class StickyComponentManager
        Inherits DependencyObject

        Public Shared ReadOnly DetachEventArgs As System.EventArgs = New System.EventArgs

        Private Shared ReadOnly StickyEventManagerProperty As DependencyProperty = DependencyProperty.RegisterAttached("StickyEventManager", GetType(IStickyComponent), GetType(StickyComponentManager), New PropertyMetadata(AddressOf OnStickyComponentChanged))
        Private Shared Function GetStickyEventManager(ByVal d As DependencyObject) As IStickyComponent
            Dim result As StickyEventCollection = d.GetValue(StickyEventManagerProperty)
            If result Is Nothing Then
                result = New StickyEventCollection
                d.SetValue(StickyEventManagerProperty, result)
            End If
            Return result
        End Function
        Private Shared Sub SetStickyEventManager(ByVal d As DependencyObject, ByVal value As IStickyComponent)
            d.SetValue(StickyEventManagerProperty, value)
        End Sub


        Public Shared Sub OnStickyComponentChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
            Dim c As IStickyComponent
            If e.OldValue IsNot e.NewValue Then
                c = TryCast(e.OldValue, IStickyComponent)
                If c IsNot Nothing Then
                    StickyComponentManager.Detach(d, c)
                End If
                c = TryCast(e.NewValue, IStickyComponent)
                If c IsNot Nothing Then
                    StickyComponentManager.Attach(d, c)
                End If
            End If
        End Sub

        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal c As IStickyComponent)
            Select Case c.Mode
                Case AttachMode.Immediate
                    AttachCore(base, c)
                Case AttachMode.OnInitialized
                    If GetIsInitialized(base) Then
                        AttachCore(base, c)
                    Else
                        Dim a As StickyComponentAttacher = New StickyComponentAttacher(c)
                        AttachInitialized(base, AddressOf a.AttachOnInitialized)
                    End If
                Case AttachMode.OnLoaded
                    If GetIsLoaded(base) Then
                        AttachCore(base, c)
                    Else
                        Dim a As StickyComponentAttacher = New StickyComponentAttacher(c)
                        AttachEvent(base, FrameworkElement.LoadedEvent, AddressOf a.AttachOnLoaded)
                    End If
            End Select
        End Sub

        Private Shared Sub AttachCore(ByVal base As DependencyObject, ByVal c As IStickyComponent)
            c.OnAttach(base, System.EventArgs.Empty)
            AttachEvent(base, FrameworkElement.UnloadedEvent, AddressOf c.OnDetach)
        End Sub

        Public Shared Sub Detach(ByVal base As DependencyObject, ByVal c As IStickyComponent)
            c.OnDetach(base, DetachEventArgs)
            DetachEvent(base, FrameworkElement.UnloadedEvent, AddressOf c.OnDetach)
        End Sub

        Public Shared Function GetIsInitialized(ByVal base As DependencyObject) As Boolean
            If TypeOf base Is FrameworkElement Then
                Return DirectCast(base, FrameworkElement).IsInitialized
            ElseIf TypeOf base Is FrameworkContentElement Then
                Return DirectCast(base, FrameworkContentElement).IsInitialized
            End If
            Return True
        End Function

        Public Shared Function GetIsLoaded(ByVal base As DependencyObject) As Boolean
            If TypeOf base Is FrameworkElement Then
                Return DirectCast(base, FrameworkElement).IsLoaded
            ElseIf TypeOf base Is FrameworkContentElement Then
                Return DirectCast(base, FrameworkContentElement).IsLoaded
            End If
            Return True
        End Function

        Public Shared Function GetCommandBindings(ByVal base As DependencyObject) As CommandBindingCollection
            Dim collection As CommandBindingCollection = Nothing
            If TypeOf base Is FrameworkElement Then
                collection = DirectCast(base, FrameworkElement).CommandBindings
            Else
                collection = DirectCast(base, FrameworkContentElement).CommandBindings
            End If
            Return collection
        End Function

        Public Shared Function GetInputBindings(ByVal base As DependencyObject) As InputBindingCollection
            Dim collection As InputBindingCollection = Nothing
            If TypeOf base Is FrameworkElement Then
                collection = DirectCast(base, FrameworkElement).InputBindings
            Else
                collection = DirectCast(base, FrameworkContentElement).InputBindings
            End If
            Return collection
        End Function

        Public Shared Function GetDataContext(ByVal base As DependencyObject) As Object
            Dim result As Object = base.GetValue(FrameworkElement.DataContextProperty)
            Return result
        End Function

        Public Shared Function GetName(ByVal base As DependencyObject) As String
            Dim result As String = base.GetValue(FrameworkElement.NameProperty)
            Return result
        End Function

        Public Shared Sub AttachDataContextChanged(ByVal base As DependencyObject, ByVal handler As DependencyPropertyChangedEventHandler)
            If TypeOf base Is FrameworkElement Then
                AddHandler DirectCast(base, FrameworkElement).DataContextChanged, handler
            ElseIf TypeOf base Is FrameworkContentElement Then
                AddHandler DirectCast(base, FrameworkContentElement).DataContextChanged, handler
            End If
        End Sub

        Public Shared Sub DetachDataContextChanged(ByVal base As DependencyObject, ByVal handler As DependencyPropertyChangedEventHandler)
            If TypeOf base Is FrameworkElement Then
                RemoveHandler DirectCast(base, FrameworkElement).DataContextChanged, handler
            ElseIf TypeOf base Is FrameworkContentElement Then
                RemoveHandler DirectCast(base, FrameworkContentElement).DataContextChanged, handler
            End If
        End Sub

        Public Shared Sub AttachEvent(ByVal base As DependencyObject, ByVal routedEvent As RoutedEvent, ByVal handler As RoutedEventHandler)
            If TypeOf base Is FrameworkElement Then
                DirectCast(base, FrameworkElement).AddHandler(routedEvent, handler)
            ElseIf TypeOf base Is FrameworkContentElement Then
                DirectCast(base, FrameworkContentElement).AddHandler(routedEvent, handler)
            End If
        End Sub

        Public Shared Sub DetachEvent(ByVal base As DependencyObject, ByVal routedEvent As RoutedEvent, ByVal handler As RoutedEventHandler)
            If TypeOf base Is FrameworkElement Then
                DirectCast(base, FrameworkElement).RemoveHandler(routedEvent, handler)
            ElseIf TypeOf base Is FrameworkContentElement Then
                DirectCast(base, FrameworkContentElement).RemoveHandler(routedEvent, handler)
            End If
        End Sub

        Public Shared Sub DetachInitialized(ByVal base As DependencyObject, ByVal handler As EventHandler)
            If TypeOf base Is FrameworkElement Then
                RemoveHandler DirectCast(base, FrameworkElement).Initialized, handler
            Else
                RemoveHandler DirectCast(base, FrameworkContentElement).Initialized, handler
            End If
        End Sub

        Public Shared Sub AttachInitialized(ByVal base As DependencyObject, ByVal handler As EventHandler)
            If TypeOf base Is FrameworkElement Then
                AddHandler DirectCast(base, FrameworkElement).Initialized, handler
            Else
                AddHandler DirectCast(base, FrameworkContentElement).Initialized, handler
            End If
        End Sub

        Public Shared Sub AttachCommandBinding(ByVal base As DependencyObject, ByVal binding As CommandBinding)
            If TypeOf base Is FrameworkElement Then
                DirectCast(base, FrameworkElement).CommandBindings.Add(binding)
            Else
                DirectCast(base, FrameworkContentElement).CommandBindings.Add(binding)
            End If
        End Sub

        Public Shared Sub DetachCommandBinding(ByVal base As DependencyObject, ByVal binding As CommandBinding)
            If TypeOf base Is FrameworkElement Then
                DirectCast(base, FrameworkElement).CommandBindings.Remove(binding)
            Else
                DirectCast(base, FrameworkContentElement).CommandBindings.Remove(binding)
            End If
        End Sub


        Public Shared Sub AttachInputBinding(ByVal base As DependencyObject, ByVal binding As InputBinding, Optional ByVal overwrite As Boolean = True)
            Dim collection As InputBindingCollection = GetInputBindings(base)
            collection.Add(binding)
        End Sub

        Public Shared Sub DetachInputBinding(ByVal base As DependencyObject, ByVal binding As InputBinding)
            If TypeOf base Is FrameworkElement Then
                DirectCast(base, FrameworkElement).InputBindings.Remove(binding)
            Else
                DirectCast(base, FrameworkContentElement).InputBindings.Remove(binding)
            End If
        End Sub

        Public Shared Sub Detach(ByVal base As DependencyObject, ByVal command As RoutedCommand)
            'Dim inputBindings As InputBindingCollection = GetInputBindings(base)
            'If inputBindings IsNot Nothing Then
            '    Dim bindings = From i As InputBinding In inputBindings
            '                   Where i.Command Is command
            '                   Select i
            '    Dim bindingList As New List(Of InputBinding)(bindings)
            '    For Each i As InputBinding In bindingList
            '        inputBindings.Remove(i)
            '    Next
            'End If
            Dim commandBindings As CommandBindingCollection = GetCommandBindings(base)
            If commandBindings IsNot Nothing Then
                Dim bindings = From c As CommandBinding In commandBindings
                               Where c.Command Is command
                               Select c
                Dim bindingList As New List(Of CommandBinding)(bindings)
                For Each c As CommandBinding In bindingList
                    commandBindings.Remove(c)
                Next
            End If
        End Sub

        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal command As RoutedCommand, ByVal executedHandler As ExecutedRoutedEventHandler, ByVal canExecuteHandler As CanExecuteRoutedEventHandler, Optional ByVal preview As Boolean = False)
            If command IsNot Nothing And executedHandler IsNot Nothing Then
                Dim b As CommandBinding = New CommandBinding(command)
                If preview Then
                    AddHandler b.PreviewExecuted, executedHandler
                    If canExecuteHandler IsNot Nothing Then
                        AddHandler b.PreviewCanExecute, canExecuteHandler
                    End If
                Else
                    AddHandler b.Executed, executedHandler
                    If canExecuteHandler IsNot Nothing Then
                        AddHandler b.CanExecute, canExecuteHandler
                    End If
                End If
                GetCommandBindings(base).Add(b)
                'For Each i As InputGesture In command.InputGestures
                '    GetInputBindings(base).Add(New InputBinding(command, i))
                'Next
            End If
        End Sub

        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal gesture As InputGesture, ByVal command As ICommand, Optional ByVal overwrite As Boolean = True)
            AttachInputBinding(base, New InputBinding(command, gesture), overwrite)
        End Sub
        Public Shared Sub Detach(ByVal base As DependencyObject, ByVal gesture As InputGesture)
            Dim collection As InputBindingCollection = GetInputBindings(base)
            If collection IsNot Nothing Then
                Dim b As InputBinding = Nothing
                For Each binding As InputBinding In collection
                    If binding.Gesture.Equals(gesture) Then
                        b = binding
                        Exit For
                    End If
                Next
                If b IsNot Nothing Then
                    collection.Remove(b)
                End If
            End If
        End Sub

        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal stickyEvent As RoutedEvent, ByVal handler As RoutedEventHandler)
            Dim eventManager As StickyEventCollection = GetStickyEventManager(base)
            Dim a As StickyEventAttacher = New StickyEventAttacher(stickyEvent, handler, False, Nothing)
            eventManager.Add(a)
        End Sub
        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal stickyEvent As RoutedEvent, ByVal handler As RoutedEventHandler, ByVal isOneShot As Boolean)
            Dim eventManager As StickyEventCollection = GetStickyEventManager(base)
            Dim a As StickyEventAttacher = New StickyEventAttacher(stickyEvent, handler, isOneShot, Nothing)
            eventManager.Add(a)
        End Sub
        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal stickyEvent As RoutedEvent, ByVal handler As RoutedEventHandler, ByVal ParamArray cancelEvents() As RoutedEvent)
            Dim eventManager As StickyEventCollection = GetStickyEventManager(base)
            Dim a As StickyEventAttacher = New StickyEventAttacher(stickyEvent, handler, True, cancelEvents)
            eventManager.Add(a)
        End Sub

        Public Shared Sub Detach(ByVal base As DependencyObject, ByVal stickyEvent As RoutedEvent, ByVal handler As RoutedEventHandler)
            Dim eventManager As StickyEventCollection = GetStickyEventManager(base)
            Dim mykey As StickyEventKey = New StickyEventKey With {.StickyEvent = stickyEvent, .EventHandler = handler}
            If eventManager.Contains(mykey) Then
                eventManager.Remove(mykey)
            End If
        End Sub

        Public Shared Sub Attach(ByVal base As DependencyObject, ByVal targetObject As DependencyObject, ByVal targetProperty As DependencyProperty, ByVal path As String, ByVal mode As BindingMode)
            Dim binding As New Binding(path)
            binding.Mode = mode
            binding.Source = base
            BindingOperations.SetBinding(targetObject, targetProperty, binding)
        End Sub

        Public Shared Sub Detach(ByVal base As DependencyObject, ByVal targetobject As DependencyObject, ByVal targetProperty As DependencyProperty)
            BindingOperations.ClearBinding(targetobject, targetProperty)
        End Sub

        Public Shared Function CreateActionDelegate(ByVal target As Object, ByVal methodName As String, ByVal actionType As Type) As [Delegate]
            Dim result As [Delegate] = Nothing

            If target IsNot Nothing AndAlso Not String.IsNullOrEmpty(methodName) AndAlso GetType([Delegate]).IsAssignableFrom(actionType) Then
                Dim targetType As System.Type = target.GetType
                Dim methodInfo As System.Reflection.MethodInfo
                methodInfo = targetType.GetMethod(methodName, actionType.GetGenericArguments)
                If methodInfo IsNot Nothing Then
                    result = [Delegate].CreateDelegate(actionType, target, methodInfo, False)
                End If
            End If

            Return result

        End Function

        Public Shared Function CreateFuncDelegate(ByVal target As Object, ByVal methodName As String, ByVal funcType As Type) As [Delegate]
            Dim result As [Delegate] = Nothing
            If target IsNot Nothing AndAlso Not String.IsNullOrEmpty(methodName) AndAlso GetType([Delegate]).IsAssignableFrom(funcType) Then
                Dim targetType As System.Type = target.GetType
                Dim methodInfo As System.Reflection.MethodInfo
                Dim paramTypes(funcType.GetGenericArguments.Length - 2) As Type
                If paramTypes.Length > 0 Then
                    Array.Copy(funcType.GetGenericArguments, paramTypes, paramTypes.Length)
                End If

                methodInfo = targetType.GetMethod(methodName, paramTypes)
                If methodInfo IsNot Nothing Then
                    result = [Delegate].CreateDelegate(funcType, target, methodInfo, False)
                End If
            End If

            Return result

        End Function

        Private Class StickyComponentAttacher
            Private _Component As IStickyComponent
            Public Sub New(ByVal c As IStickyComponent)
                MyBase.New()
                _Component = c
            End Sub

            Public Sub AttachOnInitialized(ByVal sender As Object, ByVal e As EventArgs)
                _Component.OnAttach(sender, e)
                AttachEvent(sender, FrameworkElement.UnloadedEvent, AddressOf _Component.OnDetach)
                AttachInitialized(sender, AddressOf AttachOnInitialized)
            End Sub

            Public Sub AttachOnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
                _Component.OnAttach(sender, e)
                AttachEvent(sender, FrameworkElement.UnloadedEvent, AddressOf _Component.OnDetach)
                DetachEvent(sender, FrameworkElement.LoadedEvent, AddressOf AttachOnLoaded)
            End Sub
        End Class

        Private Class StickyEventAttacher
            Implements IStickyComponent

            Private _EventHandler As RoutedEventHandler
            Private _CancelEvents() As RoutedEvent
            Private _Event As RoutedEvent
            Private _IsOneShot As Boolean

            Public Sub New(ByVal stickyEvent As RoutedEvent, ByVal handler As RoutedEventHandler, ByVal isOneShot As Boolean, ByVal cancelEvents() As RoutedEvent)
                MyBase.New()
                _EventHandler = handler
                _Event = stickyEvent
                _CancelEvents = cancelEvents
                _IsOneShot = isOneShot Or (cancelEvents IsNot Nothing AndAlso cancelEvents.Length > 0)
            End Sub

            Public ReadOnly Property StickyEvent() As RoutedEvent
                Get
                    Return _Event
                End Get
            End Property

            Public ReadOnly Property EventHandler() As RoutedEventHandler
                Get
                    Return _EventHandler
                End Get
            End Property

            Public ReadOnly Property Mode() As AttachMode Implements IStickyComponent.Mode
                Get
                    Return AttachMode.Immediate
                End Get
            End Property

            Public Sub OnAttach(ByVal sender As Object, ByVal e As System.EventArgs) Implements IStickyComponent.OnAttach
                If _IsOneShot Then
                    AttachEvent(sender, _Event, AddressOf OnOneShotEvent)
                    If _CancelEvents IsNot Nothing Then
                        For Each ev As RoutedEvent In _CancelEvents
                            AttachEvent(sender, ev, AddressOf OnCancelEvent)
                        Next
                    End If
                Else
                    AttachEvent(sender, _Event, _EventHandler)
                End If
            End Sub

            Public Sub OnDetach(ByVal sender As Object, ByVal e As System.EventArgs) Implements IStickyComponent.OnDetach
                If _IsOneShot Then
                    DetachEvent(sender, _Event, AddressOf OnOneShotEvent)
                    If _CancelEvents IsNot Nothing Then
                        For Each ev As RoutedEvent In _CancelEvents
                            DetachEvent(sender, ev, AddressOf OnCancelEvent)
                        Next
                    End If
                Else
                    DetachEvent(sender, _Event, _EventHandler)
                End If
            End Sub

            Public Sub OnOneShotEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
                _EventHandler(sender, e)
                Detach(sender, _Event, _EventHandler)
            End Sub

            Public Sub OnCancelEvent(ByVal sender As Object, ByVal e As EventArgs)
                Detach(sender, _Event, _EventHandler)
            End Sub
        End Class

        Private Structure StickyEventKey
            Implements IEquatable(Of StickyEventKey)

            Public StickyEvent As RoutedEvent
            Public EventHandler As RoutedEventHandler
            Public Overrides Function Equals(ByVal obj As Object) As Boolean
                Dim other As StickyEventKey = CType(obj, StickyEventKey)
                Return (StickyEvent Is other.StickyEvent And EventHandler = other.EventHandler)
            End Function

            Public Function Equals1(ByVal other As StickyEventKey) As Boolean Implements System.IEquatable(Of StickyEventKey).Equals
                Return Me.Equals(other)
            End Function
        End Structure

        Private Class StickyEventCollection
            Inherits StickyUtilityCollection(Of StickyEventKey, StickyEventAttacher)

            Protected Overrides Function GetKeyForItem(ByVal item As StickyEventAttacher) As StickyEventKey
                Return New StickyEventKey With {.StickyEvent = item.StickyEvent, .EventHandler = item.EventHandler}
            End Function
        End Class
    End Class
End Namespace
